﻿using System;   
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;

namespace Daco.Core
{
    public class CModuleManager<T> where T: class
    {
        /// <summary>
        /// Cache name
        /// </summary>
        protected string _cachedName = string.Empty;
        protected string CachedName
        {
            get
            {
                return "ModuleManager_" + ModuleBaseType;
            }
        }
        /// <summary>
        /// Loaded
        /// </summary>
        protected bool _isLoaded = false;
        /// <summary>
        /// Module manager is loaded or not
        /// </summary>
        public bool IsLoaded
        {
            get
            {
                return _isLoaded;
            }
        }
        /// <summary>
        /// Module base type
        /// </summary>
        private string _moduleBaseType;
        public string ModuleBaseType
        {
            get { return _moduleBaseType; }
            protected set { _moduleBaseType = value; }
        }
        /// <summary>
        /// Module class type
        /// </summary>
        private List<Type> _moduleClassTypes;
        /// <summary>
        /// Get or set module class type
        /// </summary>
        public List<Type> ModuleClassTypes
        {
            get
            {
                if (_moduleClassTypes == null && CBaseContext.Current.Items[CachedName] != null)
                {
                    _moduleClassTypes = CBaseContext.Current.Items[CachedName] as List<Type>;
                }
                return _moduleClassTypes;
            }
            set
            {
                if (value == null)
                {
                    CBaseContext.Current.Items.Remove(CachedName);
                }
                else
                {
                    CBaseContext.Current.Items[CachedName] = value;
                }
                _moduleClassTypes = value;
            }
        }
        /// <summary>
        /// Module class factories
        /// </summary>
        protected Dictionary<Type, CFactory> _moduleClassFactories = null;
        /// <summary>
        /// Gets or sets ModuleClassFactories.
        /// </summary>
        protected Dictionary<Type, CFactory> ModuleClassFactories
        {
            get
            {
                if (_moduleClassFactories == null && CBaseContext.Current.Items[CachedName + "_Factory"] != null)
                {
                    _moduleClassFactories = CBaseContext.Current.Items[CachedName + "_Factory"] as Dictionary<Type, CFactory>;
                }
                return _moduleClassFactories;
            }
            set
            {
                if (value == null)
                {
                    CBaseContext.Current.Items.Remove(CachedName + "_Factory");
                }
                else
                {
                    CBaseContext.Current.Items[CachedName + "_Factory"] = value;
                }
                _moduleClassFactories = value;
            }
        }
        /// <summary>
        /// Generic Type for the Instance Factory.
        /// </summary>
        private static Type genericFactoryType = typeof(CGenericFactory<>);
        /// <summary>
        /// Module namespace
        /// </summary>
        private string _moduleNamespace = null;
        /// <summary>
        /// Get Module namespace
        /// </summary>
        public string ModuleNamespace
        {
            get { return _moduleNamespace; }
            protected set { _moduleNamespace = value; }
        }
        /// <summary>
        /// Modules
        /// </summary>
        private List<T> _modules = new List<T>();
        /// <summary>
        /// All modules found by Module manager
        /// </summary>
        public List<T> Modules
        {
            get { return _modules; }
        }

        /// <summary>
        /// Called when the modules are loaded.
        /// </summary>
        public event EventHandler<EventArgs> LoadModules;

        /// <summary>
        /// Called when the modules are unloaded.
        /// </summary>
        public event EventHandler<EventArgs> UnloadModules;

        protected CModuleManager(string moduleNamespace, string moduleBaseType)
        {
            ModuleNamespace = moduleNamespace;
            ModuleBaseType = moduleBaseType;
        }

        public void Load()
        {
            if (!_isLoaded)
            {
                if (ModuleClassFactories == null)
                {
                    LoadFactories();
                }

                foreach (var factoryKey in ModuleClassFactories.Keys)
                {
                    _modules.Add((T)(ModuleClassFactories[factoryKey].Create()));
                }

                _isLoaded = true;

                if (LoadModules != null)
                    LoadModules(this, new EventArgs());
            }
        }

        public void Unload()
        {
            if(_isLoaded){
                _modules.Clear();

                _isLoaded = false;
                if (UnloadModules != null)
                    UnloadModules(this, new EventArgs());
            }
        }

        protected void AddModules(IList assemblies)
        {
            if (ModuleClassTypes == null)
            {
                ModuleClassTypes = FindModules(assemblies, ModuleNamespace, ModuleBaseType);
            }
            else {
                ModuleClassTypes.AddRange(FindModules(assemblies, ModuleNamespace, ModuleBaseType));
            }
        }

        protected void AddModules(List<Assembly> assemblies)
        {
            if (ModuleClassTypes == null)
            {
                ModuleClassTypes = FindModules(assemblies, ModuleNamespace, ModuleBaseType);
            }
            else
            {
                ModuleClassTypes.AddRange(FindModules(assemblies, ModuleNamespace, ModuleBaseType));
            }
        }

        protected void LoadFactories()
        {
            if (ModuleClassFactories == null)
            {
                ModuleClassFactories = new Dictionary<Type, CFactory>();

                foreach (Type module in ModuleClassTypes)
                {
                    ModuleClassFactories.Add(module, new CFactory(module));
                }
            }
        }

        protected static List<Type> FindModules(IList assemblies, string moduleNamespace, string moduleBaseInterface)
        {
            List<Type> moduleClassTypes = new List<Type>();

            foreach (Assembly ass in assemblies)
            {
                TypeFilter filter = new TypeFilter(BaseModuleFilter);

                foreach (Module module in ass.GetModules())
                {
                    foreach (Type moduleClass in module.FindTypes(filter, moduleNamespace))
                    {
                        if (moduleClass.IsAbstract) continue;

                        Type[] interfaces = moduleClass.GetInterfaces();
                        foreach (Type inter in interfaces)
                        {
                            if (inter.ToString() == moduleBaseInterface)
                            {
                                moduleClassTypes.Add(moduleClass);
                            }
                        }
                    }
                }
            }

            return moduleClassTypes;
        }

        public static bool BaseModuleFilter(Type typeObj, object criterialObj)
        {
            return typeObj.Namespace == criterialObj.ToString();
        }

        /// <summary>
        /// The factory.
        /// </summary>
        protected class CFactory
        {
            /// <summary>
            /// The generic factory.
            /// </summary>
            private CGenericFactoryBase _genericFactory;

            public CFactory(Type t)
            {
                Type initializedFactoryType = genericFactoryType.MakeGenericType(
                    new Type[]
                    {
                        typeof(T),
                        t
                    });

                _genericFactory = (CGenericFactoryBase)Activator.CreateInstance(initializedFactoryType);
            }

            public object Create()
            {
                return this._genericFactory.CreateObject();
            }
        }
        /// <summary>
        /// The generic factory.
        /// </summary>
        private class CGenericFactory<T> : CGenericFactoryBase where T : class, new()
        {
            public override object CreateObject()
            {
                return Create();
            }

            public T Create()
            {
                return new T();
            }
        }
        /// <summary>
        /// The generic factory base.
        /// </summary>
        private abstract class CGenericFactoryBase
        {
            /// <summary>
            /// Create the object
            /// </summary>
            /// <returns>The object</returns>
            public abstract object CreateObject();
        }
    }

}
